<?php
/**
 * Xinrenxinshi.php
 *
 * @author Deepseath
 * @version $Id$
 */
namespace deepseath\xinrenxinshi;

use Symfony\Component\Cache\Adapter\FilesystemAdapter;

class Xinrenxinshi
{
    /**
     * 缓存池
     * @var object
     */
    protected $_cache = null;

    /**
     * 薪人薪事应用 app key
     * @var string
     */
    protected $_appKey = '';
    /**
     * 薪人薪事应用 app secret
     * @var string
     */
    protected $_appSecret = '';

    /**
     * 薪人薪事接口服务根 URL
     * @var string
     */
    protected $_baseUrl = 'https://api.xinrenxinshi.com';

    /**
     * 薪人薪事接口版本号
     * @var string
     */
    protected $_version = 'v5';

    /**
     * 库版本号
     * @var string
     */
    protected $_libVersion = '1.0';

    /**
     * HTTP 请求对象
     * @var object
     */
    protected $_http = null;

    /**
     * 调试模式
     * @var boolean
     */
    protected $_debug = false;

    /**
     * 单点实例
     * @param array $options
     * @return \deepseath\xinrenxinshi\Xinrenxinshi
     */
    public static function &instance(array $options)
    {
        static $instance = null;
        if (empty($instance)) {
            $instance = new self($options);
        }
        return $instance;
    }

    private function __construct(array $options = [])
    {
        $this->_cache = new FilesystemAdapter('xinrenxinshi', 86400, null);
        if (function_exists('config')) {
            $config = config('xinrenxinshi');
            if (empty($options)) {
                $options = $config;
            } else {
                $options = array_merge($config, $options);
            }
            unset($config);
        }
        if (empty($options['appKey'])) {
            throw new \Exception('薪人薪事应用 appKey 未定义', 1001);
        }
        if (empty($options['appSecret'])) {
            throw new \Exception('薪人薪事应用 appSecret 未定义', 1002);
        }
        if (!empty($options['baseUrl'])) {
            $this->_baseUrl = $options['baseUrl'];
        }
        if (!empty($options['version'])) {
            $this->_version = $options['version'];
        }
        if (isset($options['debug'])) {
            $this->_debug = $options['debug'];
        }

        $this->_appKey = $options['appKey'];
        $this->_appSecret = $options['appSecret'];
        $this->_http = \Yurun\Util\HttpRequest::newSession();
    }

    /**
     * 生成签名
     * @param string $data      待签名的数据
     * @return string
     */
    public function sign(string $data)
    {
        return base64_encode(hash_hmac('sha1', $data, $this->_appSecret, true));
    }

    /**
     * 获取 token 信息
     * @return array
     * <pre>
     * + access_token   String  Y   令牌
     * + token_type     String  Y   令牌类型
     * + expires_in     String  Y   token有效时间,时间单位秒
     * + scope          String  Y   授权作用域
     * </pre>
     */
    public function getTokenInfo() : array
    {
        $token = $this->_cache->getItem('token');
        if ($token->isHit()) {
            return $token->get();
        }

        // 初始化 token 信息
        $newTokenInfo = [];
        // 构造 token 获取接口地址
        $url = $this->_baseUrl . '/authorize/oauth/token';
        $params = [
            'grant_type' => 'client_credentials',
            'client_id' => $this->_appKey,
            'client_secret' => urlencode($this->_appSecret)
        ];
        $url = $url . '?' . http_build_query($params);
        // 请求接口
        $this->_http->headers([
            'Content-Type' => 'application/json;charset=utf-8'
        ]);
        $response = $this->_http->post($url, null, 'json');
        $newTokenInfo = $response->json(true);

        if (empty($newTokenInfo) || isset($newTokenInfo['error'])) {
            throw new \Exception(isset($newTokenInfo['error']) ? json_encode($newTokenInfo) : 'request xinrenxinshi token error', 9001);
        }

        $newTokenInfo['_datetime'] = date('Y-m-d H:i:s');
        // 将本次获取的 token 值写入到缓存
        $token->set($newTokenInfo);
        if (isset($newTokenInfo['expires_in'])) {
            $expire = $newTokenInfo['expires_in'];
        } else {
            $expire = 3600;
        }
        $token->expiresAfter($expire - 60 * 5);
        $this->_cache->save($token);

        return $newTokenInfo;
    }

    /**
     * 接口 API POST
     * @param string $path  接口基于版本目录的路径
     * @param array $data   请求的数据
     * @throws \Exception
     * @return array|string
     */
    public function apiPost($path, $data)
    {
        $this->_http->headers([
            'Content-Type' => 'application/json;charset=utf-8',
            'access_token' => $this->getTokenInfo()['access_token']
        ]);
        $url = $this->_baseUrl . '/' . $this->_version . $path;
        if (!isset($data['timestamp'])) {
            $data['timestamp'] = $this->millisecond();
        }
        $data = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $sign = $this->sign($data);
        $url .= '?sign=' . urlencode($sign);
        if ($this->_debug) {
            $info = [];
            $info[] = 'api url: ' . $url;
            $info[] = 'sign: '. $sign;
            $info[] = 'body: ' . $data;
            $info[] = 'token: ' . json_encode($this->getTokenInfo());
            if (function_exists('halt')) {
                halt(implode("\r\n", $info));
            } else {
                echo implode("<br>\r\n", $info);
                exit;
            }
        }
        $response = $this->_http->post($url, $data, 'json');
        $result = $response->json(true);

        if (!isset($result['errcode']) || $result['errcode'] != 0) {
            throw new \Exception('xinrenxinshiError: ' . $result['errmsg'] . ':' . $result['errcode'] . print_r($data, true), $result['errcode']);
        }

        return isset($result['data']) ? $result['data'] : [];
    }

    /**
     * 返回毫秒时间戳
     * @return number
     */
    public function millisecond()
    {
        list($msec, $sec) = explode(' ', microtime());
        return (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
    }

    /**
     * 获取多页全部数据
     * @param string $path          接口路径
     * @param array $params         请求参数
     * @param int $maxPage          限制获取最大页码数，默认：0，不限制——获取全部数据
     * @param string $pageName      请求的页码参数名，默认：pageNo
     * @param string $resultName    返回的结果集参数名，默认：result
     * @param string $pagesName     返回的总页码参数名，默认：totalPageSize
     * @return array
     */
    public function apiPostMulti(string $path, array $params = [], int $maxPage = 0, string $pageName = 'pageNo', string $resultName = 'result', string $pagesName = 'totalPageSize') : array
    {
        $defaults = [
            $pageName => 0
        ];
        $params = array_merge($defaults, $params);
        $list = [];
        $pageNo = 0;
        while (true) {
            $params = array_merge($params, [$pageName => $pageNo]);
            $result = $this->apiPost($path, $params);
            foreach ($result[$resultName] as $row) {
                $list[] = $row;
            }
            unset($row);
            $pageNo++;
            if (($maxPage > 0 && $pageNo >= $maxPage) || !isset($result[$pagesName]) || $pageNo > $result[$pagesName]) {
                break;
            }
            unset($result);
        }
        unset($result, $params);
        return $list;
    }
}
